﻿using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using Anotar.Catel;
using Catel;
using Catel.Collections;
using Catel.IoC;
using Catel.Messaging;
using Catel.MVVM;
using Catel.Services;
using FluentValidation;
using YtbDownloader.Core.Common;
using YtbDownloader.Core.Downloaders;
using YtbDownloader.Core.Interfaces;
using YtbDownloader.Models;
using YtbDownloader.Services;
using YtbDownloader.Validators;
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
using MessageBoxButton = System.Windows.MessageBoxButton;
using MessageBoxImage = System.Windows.MessageBoxImage;

namespace YtbDownloader.ViewModels;

public partial class MainViewModel : ViewModelBase
{
    private readonly IConfigService _configService;
    private readonly IDownloader _downloader;
    private readonly IValidator<IConfig> _validator;

    public MainViewModel()
    {
        DownloadCmd = new Command(Download);
        SetOutputPathCmd = new TaskCommand(SetOutputPath);
        OpenOutputPathCmd = new Command(OpenOutputPath);
        PickCookiesCmd = new TaskCommand(PickCookies);
        OpenLogFolderCmd = new Command(OpenLogFolder);
        CopyLogCmd = new Command(CopyLog);
        ClearLogCmd = new Command(Events.Clear);
        WindowClosingCmd = new TaskCommand<CancelEventArgs>(WindowClosing);
        ServiceLocator.Default.RegisterType<IDownloader, YDownloader>();
        ServiceLocator.Default.RegisterType<IOpenExplorerService, OpenExplorerService>();
        ServiceLocator.Default.RegisterType<IClipboardService, ClipboardService>();
        ServiceLocator.Default.RegisterType<IConfigService, ConfigService>();
        ServiceLocator.Default.RegisterType<IValidator<IConfig>, ConfigValidator>();
        _downloader = ServiceLocator.Default.ResolveRequiredType<IDownloader>();
        var mediator1 = ServiceLocator.Default.ResolveRequiredType<IMessageMediator>();
        _validator = ServiceLocator.Default.ResolveRequiredType<IValidator<IConfig>>();
        _ = mediator1.Register<CommandEvent>(this, OnEventReceived);
        var configPath = Path.Combine(Catel.IO.Path.GetApplicationDataDirectory(), "Config.xml");
        _configService = ServiceLocator.Default.ResolveRequiredTypeUsingParameters<IConfigService>([configPath]);
        Config = _configService.Load<Config>();
    }

    public IConfig Config { get; }

    public bool IsBusy => _downloader.IsBusy;

    public ObservableCollection<string> Events { get; } = [];

    public double ProgressValue { get; private set; }

    public ICommand DownloadCmd { get; }

    public ICommand OpenOutputPathCmd { get; }

    public ICommand SetOutputPathCmd { get; }

    public ICommand PickCookiesCmd { get; }

    public ICommand OpenLogFolderCmd { get; }

    public ICommand CopyLogCmd { get; }

    public ICommand ClearLogCmd { get; }

    public ICommand WindowClosingCmd { get; }

    private void Download()
    {
        if (_downloader.IsBusy)
        {
            _downloader.Cancel();
        }
        else
        {
            var validation = _validator.Validate(Config);
            if (validation.IsValid)
                _downloader.Download(Config);
            else
                foreach (var failure in validation.Errors)
                    Events.Add(new CommandEvent(EventType.Log, failure.ErrorMessage));
        }
    }

    private void OpenOutputPath()
    {
        if (Directory.Exists(Config.OutputPath))
            ServiceLocator.Default.ResolveRequiredType<IOpenExplorerService>().OpenExplorer(Config.OutputPath);
        else
            Events.Add(new CommandEvent(EventType.Log, LanguageHelper.GetRequiredString("CheckOutputDirMessage")));
    }

    private async Task SetOutputPath()
    {
        var directoryService = ServiceLocator.Default.ResolveRequiredType<ISelectDirectoryService>();
        var directoryResult = await directoryService.DetermineDirectoryAsync(new DetermineDirectoryContext());
        if (directoryResult.Result) Config.OutputPath = directoryResult.DirectoryName;
    }

    private async Task PickCookies()
    {
        var openFileService = ServiceLocator.Default.ResolveRequiredType<IOpenFileService>();
        var openFileResult = await openFileService.DetermineFileAsync(new DetermineOpenFileContext
        {
            Filter = "Cookies|*.txt;*.sqlite"
        });
        if (openFileResult.Result) Config.Cookies = openFileResult.FileName;
    }

    private static void OpenLogFolder()
    {
        var path = Path.Combine(Catel.IO.Path.GetApplicationDataDirectory(), "Logs");
        ServiceLocator.Default.ResolveRequiredType<OpenExplorerService>().OpenExplorer(path);
    }

    private void CopyLog()
    {
        var builder = new StringBuilder();
        Events.ForEach(e => builder.AppendLine(e));
        ServiceLocator.Default.ResolveRequiredType<IClipboardService>().SetClipboardData(builder.ToString());
    }

    private async Task WindowClosing(CancelEventArgs e)
    {
        if (_downloader?.IsBusy == true)
        {
            if (await MessageBox.ShowAsync(LanguageHelper.GetRequiredString("ExitWarningMessage"),
                    LanguageHelper.GetRequiredString("WarningCaption"), MessageBoxButton.YesNo,
                    MessageBoxImage.Warning) == MessageBoxResult.Yes)
                await _downloader.Cancel();
            else
                e.Cancel = true;
        }

        _configService.Save(Config);
    }

    private void OnEventReceived(CommandEvent commandEvent)
    {
        switch (commandEvent.EventType)
        {
            case EventType.Started:
                DownloadStarted();
                break;

            case EventType.Completed:
                DownloadCompleted();
                break;

            case EventType.Log:
                LogReceived(commandEvent);
                break;

            default:
                return;
        }
    }

    private void LogReceived(CommandEvent commandEvent)
    {
        LogTo.Info(commandEvent.EventMessage);
        var match = ProgressRegex().Match(commandEvent.EventMessage);
        if (match.Success && (commandEvent.EventMessage.StartsWith("[download]")
                              || commandEvent.EventMessage.StartsWith(match.Value)))
        {
            var progress = double.Parse(match.Value.Split('%')[0]);
            if (ProgressValue.Equals(100.00) || progress > ProgressValue) ProgressValue = progress;
        }
        else
        {
            Application.Current.Dispatcher.Invoke(Events.Add, commandEvent.ToString());
        }
    }

    private void DownloadStarted()
    {
        ProgressValue = 0.00;
        RaisePropertyChanged(nameof(IsBusy));
    }

    private void DownloadCompleted()
    {
        RaisePropertyChanged(nameof(IsBusy));
    }

    [GeneratedRegex(@"\s*\d+\.?\d+%")]
    private static partial Regex ProgressRegex();
}